package vue;

import java.awt.Color;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;
import java.lang.management.ManagementFactory;
import java.net.URL;
import java.util.ArrayList;
import java.util.Random;

import javax.swing.ImageIcon;
import javax.swing.JPanel;
import javax.swing.Timer;
import javax.swing.UIManager;

import modele.Commune;
import modele.Doodler;
import modele.Obstacle;
import modele.ObstacleBleu;
import modele.ObstacleRouge;
import modele.ObstacleVert;
import controleur.EcouteurClavier;

/**
 * Classe de type JPanel contenant tous les éléments visuels du jeu (doodler et obstacles)
 */
public class VueDuJeu extends JPanel {

	//>>>Attributs
	private static final long serialVersionUID = 1L;

	private Doodler doodler;		//Objet Doodler
	private ArrayList<Obstacle> obstacles;		//Liste chaînée des objets Obstacles
	private Timer animator;		//Timer du jeu
	private int score;		//Score de la partie
	private int level; 	//Difficulté du jeu
	private double pasDoodler;		//Pas du doodler
	private boolean dernierObjetPercuteEstRouge;
	private boolean dernierObjetPercuteEstBleu;
	private boolean dernierObjetPercuteEstVert;
	private boolean partieEnCours;//Vrai lorsque la partie est en pause
	private boolean initialisationTerminee;
	private boolean afficherStats;
	private Random r;//Utilisé pour la génération des obstacles
	private PropertyChangeSupport ecouteurSurPositionYDoodler;
	private PropertyChangeSupport ecouteurSurPositionYObstacle;
	private int dirPrecedenteDoodler = -1; // à -1 montée +1 descente
	private Obstacle obstacleIntersectMontee; //Obstacle en collision avec le doodler lorsque ce dernier change d'état (montée/chute)
	private Font font;		//Police de caractère
	private RenderingHints rh;
	private ClassLoader cldr = this.getClass().getClassLoader();
	private boolean gauche = false;
	private boolean droite = true;
	private boolean jump = false;
	
	private URL imageURL;
	private ImageIcon img;

	//>>>Constructeur
	public VueDuJeu() {
		addKeyListener(new EcouteurClavier(this));
		obstacles=new ArrayList<Obstacle>();
		partieEnCours=false;
		afficherStats=false;
		r=new Random();
		font = new Font("LucidaSans", Font.PLAIN, 20);
		rh = new RenderingHints(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
		rh.put(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);

		ecouteurSurPositionYDoodler=new PropertyChangeSupport(this);
		ecouteurSurPositionYDoodler.addPropertyChangeListener(new PropertyChangeListener() {
			public boolean isFinDuJeu() {
				return doodler.getMaxY()>Commune.HAUTEUR && doodler.getYdir()==1;
			}

			public void propertyChange(PropertyChangeEvent evt) {
				if (isFinDuJeu()) {
					arreterPartie();
				}
			}
		});

		ecouteurSurPositionYObstacle=new PropertyChangeSupport(this);
		ecouteurSurPositionYObstacle.addPropertyChangeListener(new PropertyChangeListener() {

			public void propertyChange(PropertyChangeEvent evt) {
				Obstacle courant=(Obstacle) evt.getNewValue();
				if (courant.getY()>Commune.HAUTEUR) {
					obstacles.remove(courant);
				}
			}
		});

		animator=new Timer(Commune.DELAI_INITIAL, new ActionListener() {

			@Override
			public void actionPerformed(ActionEvent e) {
				/*Un cycle est exécuté*/
				cycle();
				/*La fenêtre est raffraichie*/
				repaint();
			}
		});

		setFocusable(true);
		setDoubleBuffered(true);

		addNotify();
	}

	//>>>Méthodes

	/**
	 * Méthode d'initialisation de la partie, lorsque la touche N est pressée.
	 */
	public void initialiserPartie() {
		initialisationTerminee = false;
		partieEnCours=true;
		score=0;
		pasDoodler=Commune.TAILLE_PAS_INITIAL;
		dernierObjetPercuteEstBleu=dernierObjetPercuteEstRouge=false;
		dernierObjetPercuteEstVert=true;
		obstacles.clear();
		obstacles.add(new ObstacleVert(200, 450));
		generationObstaclesAlea(); //Generation aleatoire d'obstacle sur presque tout l'ecran

		doodler=new Doodler((Commune.LARGEUR/2)-(Commune.DIAMETRE_DOODLER/2),Commune.HAUTEUR-Commune.DIAMETRE_DOODLER);
		
		level = 1; 	//on initialise le niveau de jeu à 1
		
		repaint();
	}

	/**
	 * Méthode d'arrêt de la partie, ce qui se passe lorsque le joueur perd.
	 */
	public void arreterPartie() {
		partieEnCours=false;
		animator.stop();
		repaint();
	}

	/**
	 * Méthode de rafraichissement des éléments visuels du jeu.
	 * @param g de type graphics
	 */
	public void paintComponent(Graphics g) {
		Graphics2D g2d=(Graphics2D)g;
		g2d.setRenderingHints(rh);//Rend le dessin plus lisse

		g2d.setColor(Color.WHITE);//Couleur de l'arrière-plan
		g2d.fillRect(0, 0, getWidth(), getHeight());//On dessine l'arrière-plan

		g2d.setFont(font);
		FontMetrics metrics = g2d.getFontMetrics(font);

		if (partieEnCours) {
			int size=obstacles.size();																																																//Récupération de la taille de la liste des obstacles
			Obstacle courant=null;																																																//On initialise un objet Obstacle courant à null
			for (int i=0;i<size;i++) {
				courant=obstacles.get(i);//On récupére l'obstacle de la liste
				if (courant instanceof ObstacleVert) {
					imageURL = cldr.getResource("images/vert.png");
				}//Si c'est un ObstacleVert, on lui attribue une plateforme verte
				else if((courant instanceof ObstacleRouge)){
					imageURL = cldr.getResource("images/rouge.png");
				}//Si c'est un ObstacleRouge, on lui attribue une plateforme rouge
				else {
					imageURL = cldr.getResource("images/bleu.png");
				}//sinon bleu
				img = new ImageIcon(imageURL);
				img.paintIcon(this, g2d,(int)courant.getX(), (int)courant.getY());		//On dessine l'obstacle
			}


			if(gauche && !jump){
				imageURL = cldr.getResource("images/djg.png");
				img = new ImageIcon(imageURL);
				img.paintIcon(this, g2d, doodler.x, doodler.y);
			}if(droite && !jump){
				imageURL = cldr.getResource("images/djd.png");
				img = new ImageIcon(imageURL);
				img.paintIcon(this, g2d, doodler.x, doodler.y);
			}if(droite && jump){
				imageURL = cldr.getResource("images/djj.png");
				img = new ImageIcon(imageURL);
				img.paintIcon(this, g2d, doodler.x, doodler.y);
				if(doodler.getYdir() == 1)jump=false;
			}if(gauche && jump){
				imageURL = cldr.getResource("images/djjg.png");
				img = new ImageIcon(imageURL);
				img.paintIcon(this, g2d, doodler.x, doodler.y);
				if(doodler.getYdir() == 1)jump=false;
			}
		}
		else {
			//GAME OVER
			String msg = "Game Over";
			g2d.setColor(Color.BLACK);
			int tailleString1 = metrics.stringWidth(msg);
			g2d.drawString(msg, getWidth()/2-tailleString1/2, getHeight()/2);
		}

		//SCORE & LEVEL
		g2d.setColor(Color.BLACK);
		g2d.fillRect(0, 0, Commune.LARGEUR, 30);
		g2d.setColor(Color.WHITE);
		String caracScore=Integer.toString(score*10);
		int tailleString2 = metrics.stringWidth(caracScore);
		g2d.drawString(caracScore, getWidth()/2-tailleString2/2, 20);
		String slevel = "level " + Integer.toString(level-1);
		int tailleString3 = metrics.stringWidth(slevel);
		g2d.drawString(slevel, getWidth()-(tailleString3+5), 20);
		
		if (afficherStats) {
			g2d.setColor(Color.BLACK);
			g2d.setFont(UIManager.getDefaults().getFont("TabbedPane.font"));
			metrics=g2d.getFontMetrics();
			String memUtilisee= "Mémoire utilisée : "+ (int)(ManagementFactory.getMemoryMXBean().getHeapMemoryUsage().getUsed()/1024) + "Ko";
			int tailleMemUtilisee=metrics.stringWidth(memUtilisee);
			g2d.drawString(memUtilisee, getWidth()-tailleMemUtilisee, 40);
			if (partieEnCours) {
				String posXDoodler="x : " + (int)doodler.getX();
				int taillePosXDoodler=metrics.stringWidth(posXDoodler);
				g2d.drawString(posXDoodler, getWidth()-taillePosXDoodler, 50);
				String posYDoodler="y : " + (int)doodler.getY();
				int taillePosYDoodler=metrics.stringWidth(posYDoodler);
				g2d.drawString(posYDoodler, getWidth()-taillePosYDoodler, 60);
				String nbObstacles="Nombre obstacles : " + obstacles.size();
				int tailleNbOstacles=metrics.stringWidth(nbObstacles);
				g2d.drawString(nbObstacles, getWidth()-tailleNbOstacles, 70);
			}
		}

		Toolkit.getDefaultToolkit().sync();								//Permet de synchroniser l'affichage des composants sur les systèmes Linux
		g.dispose();
	}

	/**
	 * Méthode exécutée à chaque cycle du timer
	 */
	private void cycle() {
		deplacerObjets();
		generationObstaclesAlea(); //Generation aleatoire d'obstacles sur la bande Y 
		augmenterDifficulté();
	}

	/**
	 * Méthode de déplacement des objets et les actions à prendre lorsque ceux-ci se percutent.
	 */
	private void deplacerObjets() {

		if ((dirPrecedenteDoodler == -1) && ( doodler.getYdir() == 1)){ // on passe en chute
			obstacleIntersectMontee = doodler.getObstaclePercute(obstacles);
		}
		dirPrecedenteDoodler = doodler.getYdir();

		Obstacle collision=null;
		if (doodler.getYdir()==1) {
			if (obstacleIntersectMontee != doodler.getObstaclePercute(obstacles)){ //permet d'éviter d'entrer en collision au milieu d'un obstacle
				collision=doodler.getObstaclePercute(obstacles);
			}

			if (collision!=null) {
				dernierObjetPercuteEstRouge=(collision instanceof ObstacleRouge);
				dernierObjetPercuteEstBleu=(collision instanceof ObstacleBleu);
				dernierObjetPercuteEstVert=(collision instanceof ObstacleVert);
			}
		}

		if (pasDoodler>1 && doodler.getYdir()==-1) {
			if (dernierObjetPercuteEstVert)pasDoodler*=Commune.COEFFICIENT_ACCELERATION_VERT; 				//Fait baisser pas (gravité)
			if (dernierObjetPercuteEstRouge){
				pasDoodler*=Commune.COEFFICIENT_ACCELERATION_ROUGE; 		//Fait baisser pas (gravité)
				jump=true;
			}
			if (dernierObjetPercuteEstBleu)pasDoodler*=Commune.COEFFICIENT_ACCELERATION_BLEU; 				//Fait baisser pas (gravité)

			if (doodler.getMinY()<(Commune.HAUTEUR/2)) { 
				/*Le doodler est à la moitié de l'ecran*/
				scrolling(); //On effectue le scrolling
			}
			else {
				doodler.montee(pasDoodler);
				ecouteurSurPositionYDoodler.firePropertyChange("Changement valeur position Doodler", 0, doodler.getY());
			}
			doodler.incrementerHauteurActuelle();
			if (doodler.getHauteurActuelle()>doodler.getHauteurMaximaleAtteinte()) {
				score++;
			}
		}
		else {
			if (collision==null) {
				if (pasDoodler<1) {
					doodler.setHauteurMaximaleAtteinte(doodler.getHauteurActuelle());
				}
				doodler.decrementerHauteurActuelle();
				doodler.setYdir(1);
				pasDoodler /= Commune.COEFFICIENT_ACCELERATION_VERT; //Ici les obstacles bleus et rouges prennent la même accélération que les verts
				doodler.descente(pasDoodler);
				ecouteurSurPositionYDoodler.firePropertyChange("Changement valeur position Doodler", 0, doodler.getY());
			}
			else {
				doodler.setYdir(-1);
				pasDoodler=Commune.TAILLE_PAS_INITIAL;
				deplacerObjets();
			}
		}
		doodler.deplacementHorizontal();
		doodler.mettreAJourPositionPoints();
		
		//Déplacement des obstacles mobiles
		for (int i=0; i<obstacles.size();i++){
			obstacles.get(i).deplacer();
		}
	}

	/**
	 * exécute le scrolling pour chaque obstacle.
	 */
	public void scrolling(){

		for (int i=0;i<obstacles.size();i++) {
			Obstacle courant = obstacles.get(i); 
			courant.scrolling(pasDoodler);
			ecouteurSurPositionYObstacle.firePropertyChange("", null, courant);
		}
	}


	/**
	 * Fonction de génération aléatoire d'obstacle intelligente. 
	 * Elle se comporte différamment suivant le type du dernier obstacle généré pour qu'un déplacement entres deux obstacles soit toujours possible 
	 * et que deux obstacles ne se chevauchent pas.  
	 */
	public void generationObstaclesAlea() {

		Obstacle dernierObstacleAjouté = obstacles.get(obstacles.size()-1);
		Obstacle tmp = null;
		Obstacle obstLePlusHaut = null;//Obstacle le plus en haut

		if(initialisationTerminee){
			obstLePlusHaut = dernierObstacleAjouté;
		}
		else{ //Initialisation
			//On crée un premier obstacle sous le doodler
			obstacles.add(new ObstacleVert((Commune.LARGEUR/2 - Commune.LARGEUR_OBSTACLE/2), (Commune.HAUTEUR-Commune.HAUTEUR_OBSTACLE)));
			obstLePlusHaut =  obstacles.get(0);
			animator.setDelay(Commune.DELAI_INITIAL);
			initialisationTerminee = true;
		}

		while (obstacles.size() != Commune.LIMITE_NB_OBSTACLES) {				

			//On génère un x et un y aléatoires où y dépend du type de l'obstacle le plus haut
			int distMaxObst;
			if (obstLePlusHaut instanceof ObstacleBleu){
				distMaxObst = Commune.DISTANCE_MAX_OBSTACLES_BLEU;
			}
			else if (obstLePlusHaut instanceof ObstacleRouge){
				distMaxObst = Commune.DISTANCE_MAX_OBSTACLES_ROUGE;
			}
			else{
				distMaxObst = Commune.DISTANCE_MAX_OBSTACLES_VERT;
			}
			int borneMaxY = (int)obstLePlusHaut.getY() - Commune.DISTANCE_MIN_OBSTACLES;
			int borneMinY = borneMaxY - distMaxObst;
			int y = borneMinY + r.nextInt(borneMaxY - borneMinY); //random entre 0 et la hauteur visible de la zone de jeu
			int x ;
			do{ //Boucle do while qui sert à éviter les obstacles qui se génère les uns au dessus des autres
				x = r.nextInt(Commune.BORNE_MAX_GEN_LARGEUR_OBSTACLES);
			}while ((x <= (obstLePlusHaut.getX() + Commune.LARGEUR_OBSTACLE*2)) && (x >= (obstLePlusHaut.getX() - Commune.LARGEUR_OBSTACLE*2)));



			//On crée le nouvel obstacle dont le type suit la probabilité COEFFICIENT_NB_OBSTACLES_VERT
			double alea = r.nextDouble();
			if (alea <= Commune.COEFFICIENT_NB_OBSTACLES_VERT){
				tmp = new ObstacleVert(x, y);
			}
			else if (alea  <= (Commune.COEFFICIENT_NB_OBSTACLES_VERT + (1-Commune.COEFFICIENT_NB_OBSTACLES_VERT)/2)){
				tmp = new ObstacleBleu(x, y);				
			}
			else{
				tmp = new ObstacleRouge(x, y);				
			}

			if(tmp.getObstaclePercute(obstacles) == null) {
				if (r.nextInt(5) == r.nextInt(5)) {
					dernierObstacleAjouté=tmp;
				}
				obstacles.add(tmp);
				obstLePlusHaut = tmp;
			}
		}
	}
	
	private void augmenterDifficulté(){
		if (score*10>level*1000){
			level ++;
			System.out.println("level UP !");
			if (animator.getDelay() > Commune.DELAI_INITIAL/2){
				animator.setDelay(Commune.DELAI_INITIAL - level);
				System.out.println("nouveau délai timer : "+(Commune.DELAI_INITIAL - level));
			}
		}
	}

	public Timer getAnimator() {
		return animator;
	}

	public boolean isAfficherStats() {
		return afficherStats;
	}

	public void setAfficherStats(boolean afficherStats) {
		this.afficherStats = afficherStats;
	}

	public Doodler getDoodler() {
		return doodler;
	}

	public ArrayList<Obstacle> getObstacles() {
		return obstacles;
	}

	public void setGauche(boolean gauche) {
		this.gauche = gauche;
	}

	public void setDroite(boolean droite) {
		this.droite = droite;
	}
}